package com.alcohol.auth.server.config;

import com.alcohol.auth.server.service.SysOAuthClientService;
import com.alcohol.auth.server.service.SysUserService;
import com.alcohol.auth.server.support.core.FormLoginAuthenticationProvider;
import com.alcohol.auth.server.support.grant.password.PasswordAuthenticationConverter;
import com.alcohol.auth.server.support.grant.password.PasswordAuthenticationProvider;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisIndexedHttpSession;

import java.util.ArrayList;
import java.util.List;

/**
 * 认证服务配置
 * @author LiXinYu
 * @date 2025/11/3
 */
@Configuration
@EnableWebSecurity
@EnableRedisIndexedHttpSession(maxInactiveIntervalInSeconds = 60)
public class AuthServerSecurityConfig {

    @Resource
    private OAuth2AuthorizationService authorizationService;
    @Resource
    private SysOAuthClientService sysOAuthClientService;
    @Resource
    private SysUserService sysUserService;

    public static final String LOGIN_PAGE = "/oauth2/login";
    public static final String CUSTOM_CONSENT_PAGE = "/oauth2/consent";

    // http://localhost:8080/oauth2/authorize?response_type=code&client_id=demo&scope=1&redirect_uri=http://www.baidu.com

    /**
     * 认证服务过滤链
     * @param http spring security核心配置类
     * @return 过滤器链
     * @throws Exception 异常
     */
    @Bean
    public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
        // 加载认证服务端点
        http.securityMatcher(this.getAuthEndpointsMatcher());

        // 认证服务过滤链拦截配置
        http.authorizeHttpRequests((authorize) -> authorize.requestMatchers(LOGIN_PAGE).permitAll().anyRequest().authenticated());

        // 获取认证管理器
//        AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
        // OAuth2配置
        OAuth2AuthorizationServerConfigurer serverConfigurer = this.buildOAuth2ServerConfigurer(authenticationManager());
        http.with(serverConfigurer, Customizer.withDefaults());

        // 当未登录时访问认证端点时重定向至login页面
//        http.exceptionHandling((exceptions) -> exceptions
//                .defaultAuthenticationEntryPointFor(
//                        new LoginTargetAuthenticationEntryPoint(),
//                        new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
//                )
//        );

        // OAuth2授权码模式——表单登录配置
        http.formLogin(formLogin -> {
            formLogin.loginPage(LOGIN_PAGE).permitAll();
            formLogin.loginProcessingUrl(LOGIN_PAGE);
        });

        // 表单登录处理器
//        http.authenticationProvider(new FormLoginAuthenticationProvider(sysUserService, passwordEncoder()));

        return http.build();
    }

    /**
     * 配置认证管理器
     */
    @Bean
    public AuthenticationManager authenticationManager() {
        return new ProviderManager(new FormLoginAuthenticationProvider(sysUserService, passwordEncoder()));
    }

    /**
     * 密码解析器
     * @return BCryptPasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置token生成器
     */
    @Bean
    OAuth2TokenGenerator<?> tokenGenerator() {
        OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
        OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
        return new DelegatingOAuth2TokenGenerator(accessTokenGenerator, refreshTokenGenerator);
    }

    /**
     * 加载认证服务端点
     * @return 认证服务端点
     */
    private RequestMatcher getAuthEndpointsMatcher() {
        List<RequestMatcher> requestMatchers = new ArrayList<>();

        AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build();
        requestMatchers.add(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, "/.well-known/oauth-authorization-server"));
        requestMatchers.add(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, authorizationServerSettings.getAuthorizationEndpoint()));
        requestMatchers.add(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, authorizationServerSettings.getDeviceVerificationEndpoint()));
        requestMatchers.add(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, authorizationServerSettings.getJwkSetEndpoint()));
        requestMatchers.add(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, LOGIN_PAGE));
        requestMatchers.add(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, CUSTOM_CONSENT_PAGE));

        requestMatchers.add(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, authorizationServerSettings.getTokenEndpoint()));
        requestMatchers.add(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, authorizationServerSettings.getTokenIntrospectionEndpoint()));
        requestMatchers.add(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, authorizationServerSettings.getTokenRevocationEndpoint()));
        requestMatchers.add(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, authorizationServerSettings.getDeviceAuthorizationEndpoint()));
        requestMatchers.add(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, authorizationServerSettings.getPushedAuthorizationRequestEndpoint()));
        requestMatchers.add(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, authorizationServerSettings.getAuthorizationEndpoint()));
        requestMatchers.add(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, authorizationServerSettings.getDeviceVerificationEndpoint()));
        requestMatchers.add(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, LOGIN_PAGE));

        return new OrRequestMatcher(requestMatchers);
    }

    /**
     * 自定义OAuth2配置
     * @param authenticationManager 认证管理器
     * @return OAuth2配置
     */
    private OAuth2AuthorizationServerConfigurer buildOAuth2ServerConfigurer(AuthenticationManager authenticationManager) {
        OAuth2AuthorizationServerConfigurer oAuth2ServerConfigurer = OAuth2AuthorizationServerConfigurer.authorizationServer();
        // 开启OpenID Connect 1.0协议相关端点
        oAuth2ServerConfigurer.oidc(Customizer.withDefaults());
        // tokenEndpoint配置
        oAuth2ServerConfigurer.tokenEndpoint(tokenEndpoint -> {
            // 密码模式
            tokenEndpoint.accessTokenRequestConverter(new PasswordAuthenticationConverter())
                    .authenticationProvider(new PasswordAuthenticationProvider(authorizationService, tokenGenerator(), sysOAuthClientService, authenticationManager));
        });
        // 设置自定义用户确认授权页
//        oAuth2ServerConfigurer.authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE));

        return oAuth2ServerConfigurer;
    }
}
